package de.javakicker;

import java.awt.Point;
import java.awt.Dimension;
import java.util.*;

/**
 * Eine abstrakte Basisklasse fuer alle Spieler.
 *
 * @author Mathias Retzlaf
 * @author Hendrik Dahlkamp
 * @author Holger Kujath
 * @author Till Krullmann
 */
public abstract class Player
{
	
	/** Symbolische Konstante fuer 'Nichts'. */
	public static final int NOTHING = 0;

	/** Symbolische Konstante fuer 'Hoch'. */
	public static final int UP = 1;

	/** Symbolische Konstante fuer 'Runter'. */
	public static final int DOWN = 2;

	/** Symbolische Konstante fuer 'Links'. */
	public static final int LEFT = 4;

	/** Symbolische Konstante fuer 'Rechts'. */
	public static final int RIGHT = 8;

	/** Maske fuer 'Hoch' oder 'Runter'. */
	public static final int VERTICAL = UP | DOWN;

	/** Maske fuer 'Links' oder 'Rechts'. */
	public static final int HORIZONTAL = LEFT | RIGHT;

	/** Symbolische Konstante fuer ein leeres Feld. */
	public static final int FREE = 0;

	/** Symbolische Konstante fuer ein Feld, auf dem ein Roboter vom gleichen Team steht. */
	public static final int MY_TEAM = 1;

	/** Symbolische Konstante fuer ein Feld, auf dem ein Roboter vom gegnerischen Team steht. */
	public static final int OTHER_TEAM = 2;

	/** Symbolische Konstante fuer ein Feld, auf dem der Ball liegt. */
	public static final int BALL = 3;

	/** Symbolische Konstante fuer ein Feld, welches ausserhalb des regulaeren Spielfeldes liegt. */
	public static final int WALL = 4;


	/** The game info. */
	private GameInfo gameInfo = null;

	/**
	 * Sets the game info.
	 *
	 * @param gameInfo the new game info
	 */
	final void setGameInfo(GameInfo gameInfo)
	{
		this.gameInfo = gameInfo;
	}


	/**
	 * Liefert die Position des Spielers als <code>Point</code>-Objekt.
	 * @return die Position des Spielers
	 */
	protected final Point getMyPos()
	{
		return gameInfo.getPlayerPosition(this);
	}


	/**
	 * Liefert die horizontale Position des Spielers.
	 * @return die X-Koordinate des Spielers
	 */
	protected final int getMyXPos()
	{
		return getMyPos().x;
	}


	/** Liefert die vertikale Position des Spielers.
	 * @return die Y-Koordinate des Spielers
	 */
	protected final int getMyYPos()
	{
		return getMyPos().y;
	}

	/**
	 * Liefert die Groesse (Breite und Hoehe) des Spielfelds. Die Spielrichtung
	 * ist dabei von unten nach oben.
	 * @return ein <code>Dimension</code>-Objekt, das die Breite und Hoehe
	 * enthaelt
	 */
	protected final Dimension getFieldSize()
	{
		return gameInfo.getFieldSize();
	}


	/** Liefert die Breite des Spielfelds.
	 * @return die Breite des Spielfelds
	 */
	protected final int getFieldWidth()
	{
		return getFieldSize().width;
	}


	/** Liefert die Hoehe des Spielfelds.
	 * @return die Hoehe des Spielfelds
	 */
	protected final int getFieldHeight()
	{
		return getFieldSize().height;
	}


	/** Liefert die Position des Balles als <code>Point</code>-Objekt.
	 * @return die Position des Balles
	 */
	protected final Point getBallPos()
	{
		return gameInfo.getBallPos(this);
	}

	/** Liefert die horizontale Position des Balles.
	 * @return die X-Koordinate des Balles
	 */
	protected final int getBallXPos()
	{
		return getBallPos().x;
	}

	/** Liefert die vertikale Position des Balles.
	 * @return die Y-Koordinate des Balles
	 */
	protected final int getBallYPos()
	{
		return getBallPos().y;
	}


	/**
	 * Fuehrt eine Spielaktion aus.
	 * Diese Methode <em>muss</em> in der Subklasse ueberschrieben werden. Hier
	 * wird der eigentliche Algorithmus der Roboter implementiert.
	 * @return die Aktion, die der Roboter in dieser Runde ausf�hren soll
	 * @see SoccerAction
	 */
	public abstract SoccerAction makeMove();


	/**
	 * Liefert Informationen ueber den Inhalt eines bestimmten Spielfeldes.
	 * @param pos der Punkt auf dem Spielfeld, dessen Inhalt ermittelt werden
	 *            soll
	 * @return Der Inhalt des Spielfelds; eine der folgenden Konstanten:
	 * <ul>
	 * <li><code>FREE</code>, falls das Feld frei ist</li>
	 * <li><code>MY_TEAM</code>, falls auf dem Feld ein Roboter des eigenen
	 * Teams steht</li>
	 * <li><code>OTHER_TEAM</code>, falls auf dem Feld ein Roboter des gegnerischen
	 * Teams steht</li>
	 * <li><code>BALL</code>, falls der Ball auf diesem Feld liegt</li>
	 * <li><code>WALL</code>, falls das Feld ausserhalb des Spielplans liegt</li>
	 * </ul>
	 * @see #lookAt(int, int)
	 * @see #lookTo
	 */
	protected final int lookAt(Point pos)
	{
		return gameInfo.getCellInfo(this, pos);
	}


	/**
	 * Liefert Informationen ueber den Inhalt eines bestimmten Spielfeldes.
	 * Dies entspricht der Methode <code>lookAt(Point)</code>; die Koordinaten des
	 * Punktes koennen jedoch einzeln angegeben werden.
	 * @param x die X-Koordinate des Feldes
	 * @param y die Y-Koordinate des Feldes
	 * @return Der Inhalt des Spielfelds. Entspricht dem Rueckgabewert von
	 *         <code>lookAt(Point)</code>.
	 * @see #lookAt(Point)
	 * @see #lookTo
	 */
	protected final int lookAt(int x, int y)
	{
		return gameInfo.getCellInfo(this, new Point(x, y));
	}


	/**
	 * Liefert Informationen ueber ein Feld in einer bestimmten Richtung.
	 * @param direction die Lage des Feldes in Bezug auf den Roboter, als Richtung:
	 * <ul>
	 * <li><code>UP</code> - oben</li>
	 * <li><code>DOWN</code> - unten</li>
	 * <li><code>LEFT</code> - links</li>
	 * <li><code>RIGHT</code> - rechts</li></ul>
	 * @param distance die Entfernung des Feldes vom Roboter
	 * @return Der Inhalt des Spielfelds. Entspricht dem Rueckgabewert von
	 *         <code>lookAt(Point)</code>.
	 * @see #lookTo(int)
	 * @see #lookToRel
	 * @see #lookAt
	 */
	protected final int lookTo(int direction, int distance)
	{
		Point pos = getMyPos();

		switch (direction)
		{
			case UP:
				pos.y -= distance;
				break;
			case DOWN:
				pos.y += distance;
				break;
			case LEFT:
				pos.x -= distance;
				break;
			case RIGHT:
				pos.x += distance;
				break;
			default:
				return NOTHING;
		}

		return lookAt(pos);
	}



	/**
	 * Liefert Informationen ueber den Inhalt eines der vier Felder um den Roboter
	 * herum.
	 * Dies entspricht einem Aufruf von <code>lookToRel( getMyPos(), direction )</code>.
	 * @param direction die Lage des Feldes in Bezug auf den Roboter, als Richtung:
	 * <ul>
	 * <li><code>UP</code> - oben</li>
	 * <li><code>DOWN</code> - unten</li>
	 * <li><code>LEFT</code> - links</li>
	 * <li><code>RIGHT</code> - rechts</li></ul>
	 * @return Der Inhalt des Spielfelds. Entspricht dem Rueckgabewert von
	 *         <code>lookAt</code>.
	 * @see #lookTo(int, int)
	 * @see #lookToRel
	 * @see #lookAt
	 */
	protected final int lookTo(int direction)
	{
		return lookTo(direction, 1);
	}


	/**
	 * Liefert Informationen ueber den Inhalt eines Feldes, das in einer bestimmten Richtung
	 * und in einer bestimmten Entfernung von einem anderen Feld liegt.
	 * Dies entspricht einem Aufruf von
	 * <code>lookAt( offsetPoint(from, direction, distance) )</code>.
	 * @param from ein Feld, das als Ausgangspunkt dient
	 * @param direction die Richtung, in der das Zielfeld von <code>from</code> aus
	 *        gesehen liegt
	 * @param distance die Entfernung zwischen <code>from</code> und dem Zielfeld
	 * @return Der Inhalt des Feldes. Entspricht dem Rueckgabewert von
	 *         <code>lookAt</code>.
	 * @see #lookToRel(Point, int)
	 * @see #lookTo
	 * @see #lookAt
	 */
	protected final int lookToRel(Point from, int direction, int distance)
	{
		return lookAt(offsetPoint(from, direction, distance));
	}


	/**
	 * Liefert Informationen ueber den Inhalt eines Feldes, das in einer bestimmten Richtung
	 * und in einer bestimmten Entfernung von einem anderen Feld liegt.
	 * Dies entspricht einem Aufruf von
	 * @param xFrom die X-Koordinate des Ausgangsfeldes
	 * @param yFrom die Y-Koordinate des Ausgangsfeldes
	 * @param direction die Richtung, in der das Zielfeld von
	 *        (<code>xFrom</code>, <code>yFrom</code>) aus gesehen liegt
	 * @param distance die Entfernung zwischen (<code>xFrom</code>, <code>yFrom</code>) und
	 *        dem Zielfeld
	 * @return Der Inhalt des Feldes. Entspricht dem Rueckgabewert von
	 *         <code>lookAt</code>.
	 * @see #lookToRel(Point, int)
	 * @see #lookTo
	 * @see #lookAt
	 */
	protected final int lookToRel(int xFrom, int yFrom, int direction, int distance)
	{
		return lookToRel(new Point(xFrom, yFrom), direction, distance);
	}


	/**
	 * Liefert Informationen ueber den Inhalt eines Feldes, das in einer bestimmten Richtung
	 * neben einem anderen Feld liegt.
	 * Dies entspricht einem Aufruf von
	 * <code>lookAt( offsetPoint(from, direction) )</code>.
	 * @param from ein Feld, das als Ausgangspunkt dient
	 * @param direction die Richtung, in der das Zielfeld von <code>from</code> aus
	 *        gesehen liegt
	 * @return Der Inhalt des Feldes. Entspricht dem Rueckgabewert von
	 *         <code>lookAt(Point)</code>.
	 * @see #lookToRel(Point, int, int)
	 * @see #lookTo
	 * @see #lookAt
	 */
	protected final int lookToRel(Point from, int direction)
	{
		return lookToRel(from, direction, 1);
	}



	/**
	 * Liefert Informationen ueber den Inhalt eines Feldes, das in einer bestimmten Richtung
	 * neben einem anderen Feld liegt.
	 * @param xFrom die X-Koordinate des Ausgangsfeldes
	 * @param yFrom die Y-Koordinate des Ausgangsfeldes
	 * @param direction die Richtung, in der das Zielfeld von
	 *        (<code>xFrom</code>, <code>yFrom</code>) aus gesehen liegt
	 * @return Der Inhalt des Feldes. Entspricht dem R�ckgabewert von
	 *         <code>lookAt</code>.
	 * @see #lookToRel(Point, int)
	 * @see #lookTo
	 * @see #lookAt
	 */
	protected final int lookToRel(int xFrom, int yFrom, int direction)
	{
		return lookToRel(xFrom, yFrom, direction, 1);
	}


	/** Fragt ab, ob der Ball neben dem aktuellen Roboter liegt.
	 * Dies ist eine Kurzform fuer
	 * <blockquote><code>ballAround(getMyPos())</code></blockquote>
	 * @return Die Lage des Balles, bezogen auf den Spieler. Entspricht dem
	 *         Rueckgabewert von <code>ballAround</code>.
	 * @see #ballAround
	 */
	protected final int selfBallAround()
	{
		Point myPos = getMyPos();
		return (ballAround(myPos.x, myPos.y));
	}


	/** Fragt ab, ob der Ball neben einem bestimmten Feld liegt.
	 * @param pos die Position des Feldes
	 * @return Die Lage des Balles, bezogen auf das Feld. Dies ist eine der
	 *         folgenden Konstanten:
	 * <ul>
	 * <li><code>UP</code>, <code>DOWN</code>, <code>LEFT</code> oder
	 * <code>RIGHT</code> fuer die Richtung des Balles relativ zum Feld</li>
	 * <li><code>BALL</code>, falls der Ball selbst auf dem Feld liegt</li>
	 * <li>Falls Ball <code>NOTHING</code> falls der Ball nicht neben dem
	 * Feld liegt.</li></ul>
	 */
	protected final int ballAround(Point pos)
	{
		return ballAround(pos.x, pos.y);
	}


	/**
	 * Fragt ab, ob der Ball neben einem bestimmten Feld liegt.
	 * @param x die X-Koordinate des Feldes
	 * @param y die Y-Koordinate des Feldes
	 * @return Die Lage des Balles, bezogen auf das Feld. Entspricht dem
	 *         Rueckgabewert von <code>ballAround(Point)</code>
	 * @see #ballAround(Point)
	 * @see #selfBallAround
	 */
	protected final int ballAround(int x, int y)
	{
		if (lookAt(x, y - 1) == BALL)
			return UP;
		else if (lookAt(x, y + 1) == BALL)
			return DOWN;
		else if (lookAt(x - 1, y) == BALL)
			return LEFT;
		else if (lookAt(x + 1, y) == BALL)
			return RIGHT;
		else
			return NOTHING;
	}


	/**
	 * Ermittelt die bestmoegliche Bewegung zum Erreichen des Balls.
	 * @param direction die Richtung der Bewegung
	 * @return SoccerAction Eine <code>SoccerAction</code>, die eine entsprechende
	 *        Bewegung enth�lt: <code>MOVE</code>, falls noetig, ansonsten
	 *        <code>RUN</code>.
	 * @see SoccerAction
	 * @see SoccerAction#move
	 * @see SoccerAction#run
	 */
	protected final SoccerAction moveOrRun(int direction)
	{
		Point myPos = getMyPos();
		switch (direction)
		{
			case UP:
			case DOWN:
				if (Math.abs(getBallYPos() - myPos.y) >= 2)
					return SoccerAction.run(direction);
				else
					return SoccerAction.move(direction);
			case LEFT:
			case RIGHT:
				if (Math.abs(getBallXPos() - myPos.x) >= 2)
					return SoccerAction.run(direction);
				else
					return SoccerAction.move(direction);
			default:
				throw new IllegalArgumentException("Ung�ltige Richtung");
		}
	}


	/**
	 * Findet alle Roboter aus einem Team.
	 * @param team eine Konstante, die das Team bezeichnet; entweder <code>MY_TEAM</code>
	 *        oder <code>OTHER_TEAM</code>.
	 * @return ein Array aus <code>Point</code>-Objekten, das die Positionen aller
	 *         Roboter im Team enth�lt. 
	 */
	protected final Point[] findRobots(int team)
	{
		Collection<Point> teamRobots = new ArrayList<Point>(5);
		
		Point p = new Point();
		for (p.x = 0; p.x < getFieldWidth(); p.x++)
		{
			for (p.y = 0; p.y < getFieldHeight(); p.y++)
			{
				if (lookAt(p) == team)
					teamRobots.add(new Point(p));
			}
		}
		
		Point[] pointsArray = new Point[teamRobots.size()];
		teamRobots.toArray(pointsArray);
		
		return pointsArray;
	}


	/**
	 * Ermittelt den Klassennamen eines Roboters im eigenen Team.
	 * @param pos die Position des eigenen Roboters
	 * @return der Klassenname des befreundeten Roboters, oder <code>null</code>, falls
	 *         auf dem Feld kein befreundeter Roboter steht
	 * @see #findRobotsOfClass
	 */
	protected final String getRobotClassName(Point pos)
	{
		RuntimePlayer thisPlayer = gameInfo.getPlayerAt(getMyPos(), this);
		RuntimePlayer otherPlayer = gameInfo.getPlayerAt(pos, this);
		if (otherPlayer != null && otherPlayer.getTeam().equals(thisPlayer.getTeam()))
		{
			String className = otherPlayer.getPlayer().getClass().getName();
			return className;
		}
		else
			return null;
	}


	/**
	 * Findet alle befreundeten Roboter einer bestimmten Klasse.
	 * @param className Der Name der Roboterklasse.
	 * @return Ein Array, das alle Roboter des eigenen Teams enthaelt, die eine Instanz der
	 *         Klasse sind. 
	 * @see #getRobotClassName
	 */
	protected final Point[] findRobotsOfClass(String className)
	{
		Collection<RuntimePlayer> robots = gameInfo.getFieldRobots(MY_TEAM, this);
		ArrayList<Point> positions = new ArrayList<Point>(robots.size());
		for (Iterator<RuntimePlayer> i = robots.iterator(); i.hasNext();)
		{
			RuntimePlayer player = (RuntimePlayer) i.next();
			if (player.getClass().getName().equals(className))
				positions.add(player.getPosition());
		}

		Point[] posArray = new Point[positions.size()];
		positions.toArray(posArray);
		return posArray;
	}
	
	
	/**
	 * Findet alle befreundeten Roboter einer bestimmten Klasse.
	 * @param classNames ein Array aus <code>String</code>s, das die Namen der Roboterklassen
	 *        enthaelt
	 * @return Ein Array, das alle Roboter des eigenen Teams enthaelt, die eine Instanz einer
	 *         der angegebenen Klassen sind. 
	 * @see #getRobotClassName
	 */
	protected final Point[] findRobotsOfClasses(String[] classNames)
	{
		if (classNames.length == 0)
			return new Point[] { };
		
		Collection<RuntimePlayer> robots = gameInfo.getFieldRobots(MY_TEAM, this);
		ArrayList<Point> positions = new ArrayList<Point>(robots.size());
		for (Iterator<RuntimePlayer> i = robots.iterator(); i.hasNext();)
		{
			RuntimePlayer player = (RuntimePlayer) i.next();
			for (int j = 0; j < classNames.length; j++)
			{
				if (player.getClass().getName().equals(classNames[j]))
				{
					positions.add(player.getPosition());
					break;
				}
			}
		}
		
		Point[] posArray = new Point[positions.size()];
		positions.toArray(posArray);
		return posArray;
	}
	
	
	/**
	 * Ermittelt, ob auf einer Verbindung zwischen zwei Punkten alle Felder frei sind.
	 * Der Start- und Endpunkt werden dabei <em>nicht</em> mitgezaehlt.
	 * @param from der Startpunkt der Verbindung
	 * @param to der Endpunkt der Verbindung
	 * @return <code>true</code>, falls die Verbindung frei ist. <code>false</code>,
	 *         falls ein Hindernis dazwischen ist oder die Punkte nicht auf einer Linie
	 *         liegen.
	 */
	protected final boolean isLineFree(Point from, Point to)
	{
		int dir = getRelativeDirection(from, to);
		if ((dir & HORIZONTAL) != NOTHING && (dir & VERTICAL) != NOTHING)
			return false; 
		
		Point pos = offsetPoint(from, dir);
		while (!pos.equals(to))
		{
			if (lookAt(pos) != FREE)
				return false;
			pos = offsetPoint(pos, dir);
		}

		return true;
	}
	
	
	/**
	 * Ermittelt, welcher eigene Roboter einen ausgefuehrten Pass empfangen wuerde. Die Methode
	 * prueft aber nur, ob die Verbindung zwischen den beiden Spielern frei ist; der Ball
	 * muss nicht unbedingt schon in dieser Richtung liegen.
	 * @param direction die Richtung, in die gepasst werden soll
	 * @return die Koordinaten des empfangenden Roboters
	 */
	protected final Point getPassReceiver(int direction)
	{
		Point pos = getMyPos();
		
		do
			pos = offsetPoint(pos, direction);
		while (lookAt(pos) == FREE || lookAt(pos) == BALL);
		
		if (lookAt(pos) == MY_TEAM)
			return pos;
		else
			return null;
	}


	/**
	 * Prueft, ob der aktuelle Roboter in eine Richtung passen koennte. Die Methode prueft aber
	 * nur, ob die Verbindung zwischen den beiden Spielern frei ist; der Ball muss nicht
	 * unbedingt schon in dieser Richtung liegen.
	 * @param direction die Richtung, in die gepasst werden soll
	 * @return <code>true</code>, falls Pass zu einem Teamroboter m�glich,
	 *         ansonsten <code>false</code>
	 * @see #isPassPossible
	 */
	protected final boolean isSelfPassPossible(int direction)
	{
		return getPassReceiver(direction) != null;
	}


	/**
	 * Prueft, ob ein Roboter, der auf einem bestimmten Feld steht, in eine
	 * bestimmte Richtung passen kann.
	 * @param pos die Position des passenden Roboters
	 * @param direction Die Richtung, in die gepasst werden soll. Die Richtung
	 *        wird dabei nie gedreht, d.h. ein <code>UP</code>-Pass fuer einen
	 *        gegnerischen Roboter waere ein Rueckpass.
	 * @return <code>true</code>, falls Pass zu einem Teamroboter moeglich,
	 *         ansonsten <code>false</code>
	 * @see #isPassPossible(int, int, int)
	 * @see #isSelfPassPossible
	 */
	protected final boolean isPassPossible(Point pos, int direction)
	{
		switch (lookAt(pos))
		{
			case MY_TEAM:
				return isSelfPassPossible(direction);
			case OTHER_TEAM:
				return gameInfo.getPlayerAt(pos, this)
					.getPlayer().isSelfPassPossible(direction);
			default:
				return false;
		}
	}

	/**
	 * Prueft, ob ein Roboter, der auf einem bestimmten Feld steht, in eine
	 * bestimmte Richtung passen kann.
	 * @param x die X-Koordinate des passenden Roboters
	 * @param y die Y-Koordinate des passenden Roboters
	 * @param direction Die Richtung, in die gepasst werden soll. Die Richtung
	 *        wird dabei nie gedreht, d.h. ein <code>UP</code>-Pass f�r einen
	 *        gegnerischen Roboter waere ein Rueckpass.
	 * @return <code>true</code>, falls Pass zu einem Teamroboter moeglich,
	 *         ansonsten <code>false</code>
	 * @see #isPassPossible(Point, int)
	 * @see #isSelfPassPossible
	 */
	protected final boolean isPassPossible(int x, int y, int direction)
	{
		return isPassPossible(new Point(x, y), direction);
	}


	/**
	 * Prueft, ob dieser Roboter am weitesten von allen Teamrobotern vom Ball
	 * entfernt ist.
	 * @return <code>true</code>, falls Roboter am weitesten entfernt ist,
	 *         ansonsten <code>false</code>
	 */
	protected final boolean selfMostAway()
	{
		Point myPos = getMyPos(), ballPos = getBallPos(),
			p = new Point();
		for (; p.x < getFieldWidth(); p.x++)
			for (; p.y < getFieldHeight(); p.y++)
			{
				if ( (lookAt(p.x, p.y) == MY_TEAM) &&
					myPos.distance(ballPos) < p.distance(ballPos))
				{
					return false;
				}
			}
		return true;
	}


	/**
	 * Erfragt die Prioritaet eines Roboters auf einem bestimmten Feld. Die Prioritaet
	 * bestimmt, wann der Roboter das naechste Mal eine Aktion ausfuehren darf.
	 * @param pos die Position des Feldes
	 * @return Die Prioritaet im Verhaeltnis zu der des aktuellen Roboters.
	 *         0 ist hierbei die hoechste Prioritaet; hoehere Zahlen bedeuten
	 *         niedrigere Prioritaeten.<br/>Steht auf dem Feld kein Spieler, wird
	 *         <code>Integer.MAX_VALUE</code> zurueckgegeben.
	 * @see #getPriority(int, int)
	 * @see #nextRobotAround
	 * @see #nextOwnRobotAround
	 * @see #nextOtherRobotAround
	 */
	protected final int getPriority(Point pos)
	{
		if (pos.x < 0 || pos.x >= getFieldWidth() ||
			pos.y < 0 || pos.y >= getFieldHeight())
			return Integer.MAX_VALUE;
		
		RuntimePlayer rtPlayer = gameInfo.getPlayerAt(pos, this);
		
		if (rtPlayer == null)
			return Integer.MAX_VALUE;
		else
		{
			Player player = rtPlayer.getPlayer();
			if (player == null)
				return Integer.MAX_VALUE;
			else
				return gameInfo.getPlayerPriority(player);
		}
	}


	/**
	 * Erfragt die Prioritaet eines Roboters auf einem bestimmten Feld. Die Prioritaet
	 * bestimmt, wann der Roboter das naechste Mal eine Aktion ausfuehren darf.
	 * @param x die X-Koordinate des Feldes
	 * @param y die Y-Koordinate des Feldes
	 * @return Die Prioritaet im Verhaeltnis zu der des aktuellen Roboters.
	 *         0 ist hierbei die hoechste Prioritaet; hoehere Zahlen bedeuten
	 *         niedrigere Prioritaeten. Steht auf dem Feld kein Spieler, wird
	 *         <code>Integer.MAX_VALUE</code> zurueckgegeben.
	 * @see #getPriority(Point)
	 * @see #nextRobotAround
	 * @see #nextOwnRobotAround
	 * @see #nextOtherRobotAround
	 */
	protected final int getPriority(int x, int y)
	{
		return getPriority(new Point(x, y));
	}



	/**
	 * Sucht in der Umgebung eines Feldes nach dem Roboter mit der hoechsten
	 * Prioritaet.
	 * @param pos die Position des Feldes
	 * @return die Richtung des Roboters in Bezug auf das Feld
	 *         <nobr>(<code>x</code>, <code>y</code>)</nobr>, oder
	 *         <code>NOTHING</code>, falls es keine benachbarten Roboter gibt.
	 * @see #nextRobotAround(int, int)
	 * @see #getPriority
	 * @see #nextOwnRobotAround
	 * @see #nextOtherRobotAround
	 */
	protected final int nextRobotAround(Point pos)
	{
		return nextRobotAround(pos.x, pos.y);
	}


	/**
	 * Sucht in der Umgebung eines Feldes nach dem Roboter mit der hoechsten
	 * Prioritaet.
	 * @param x die X-Koordinate des Feldes
	 * @param y die Y-Koordinate des Feldes
	 * @return die Richtung des Roboters in Bezug auf das Feld
	 *         <nobr>(<code>x</code>, <code>y</code>)</nobr>, oder
	 *         <code>NOTHING</code>, falls es keine benachbarten Roboter gibt.
	 * @see #nextRobotAround(Point)
	 * @see #getPriority
	 * @see #nextOwnRobotAround
	 * @see #nextOtherRobotAround
	 */
	protected final int nextRobotAround(int x, int y)
	{
		int bestDirection = UP;
		int bestPriority = getPriority(x, y - 1);
		if (getPriority(x, y + 1) < bestPriority)
		{
			bestDirection = DOWN;
			bestPriority = getPriority(x, y + 1);
		}
		if (getPriority(x + 1, y) < bestPriority)
		{
			bestDirection = RIGHT;
			bestPriority = getPriority(x + 1, y);
		}
		if (getPriority(x - 1, y) < bestPriority)
		{
			bestDirection = LEFT;
			bestPriority = getPriority(x - 1, y);
		}
		if (bestPriority == Integer.MAX_VALUE)
			return NOTHING;
		return bestDirection;
	}


	/**
	 * Next team robot around.
	 *
	 * @param x the x
	 * @param y the y
	 * @param team the team
	 * @return the int
	 */
	private int nextTeamRobotAround(int x, int y, int team)
	{
		int bestDirection = UP;
		int bestPriority = (lookAt(x, y - 1) == team) ? getPriority(x, y - 1) :
			Integer.MAX_VALUE;
		if ( (lookAt(x, y + 1) == team) && (getPriority(x, y + 1) < bestPriority))
		{
			bestDirection = DOWN;
			bestPriority = getPriority(x, y + 1);
		}
		if ( (lookAt(x + 1, y) == team) && (getPriority(x + 1, y) < bestPriority))
		{
			bestDirection = RIGHT;
			bestPriority = getPriority(x + 1, y);
		}
		if ( (lookAt(x - 1, y) == team) && (getPriority(x - 1, y) < bestPriority))
		{
			bestDirection = LEFT;
			bestPriority = getPriority(x - 1, y);
		}
		if (bestPriority == Integer.MAX_VALUE)
			return NOTHING;
		return bestDirection;
	}


	/**
	 * Sucht in der Umgebung eines Feldes nach dem Roboter mit der hoechsten
	 * Prioritaet aus dem eigenen Team.
	 * @param pos die Position des Feldes
	 * @return die Richtung des Roboters in Bezug auf das Feld
	 *         <nobr>(<code>x</code>, <code>y</code>)</nobr>, oder
	 *         <code>NOTHING</code>, falls es keine benachbarten eigenen Roboter
	 *         gibt.
	 * @see #nextOwnRobotAround(int, int)
	 * @see #nextOwnRobotAround(int)
	 * @see #getPriority
	 * @see #nextRobotAround
	 * @see #nextOtherRobotAround
	 */
	protected final int nextOwnRobotAround(Point pos)
	{
		return nextOwnRobotAround(pos.x, pos.y);
	}


	/**
	 * Sucht in der Umgebung eines Feldes nach dem Roboter mit der hoechsten
	 * Prioritaet aus dem eigenen Team.
	 * @param x die X-Koordinate des Feldes
	 * @param y die Y-Koordinate des Feldes
	 * @return die Richtung des Roboters in Bezug auf das Feld
	 *         <nobr>(<code>x</code>, <code>y</code>)</nobr>, oder
	 *         <code>NOTHING</code>, falls es keine benachbarten eigenen Roboter
	 *         gibt.
	 * @see #nextOwnRobotAround(Point)
	 * @see #nextOwnRobotAround(int)
	 * @see #getPriority
	 * @see #nextRobotAround
	 * @see #nextOtherRobotAround
	 */
	protected final int nextOwnRobotAround(int x, int y)
	{
		return nextTeamRobotAround(x, y, MY_TEAM);
	}


	/**
	 * Sucht in der Umgebung eines Feldes, das neben diesem Roboter liegt, nach dem Roboter
	 * mit der hoechsten Prioritaet aus dem eigenen Team.
	 * @param direction die Richtung, in der das Feld liegt
	 * @return die Richtung des Roboters in Bezug auf das Feld, oder
	 *         <code>NOTHING</code>, falls es keine benachbarten eigenen
	 *         Roboter gibt.
	 * @see #nextOwnRobotAround(Point)
	 * @see #nextOwnRobotAround(int, int)
	 * @see #getPriority
	 * @see #nextRobotAround
	 * @see #nextOtherRobotAround
	 */
	protected final int nextOwnRobotAround(int direction)
	{
		return nextOwnRobotAround(offsetPoint(getMyPos(), direction));
	}


	/**
	 * Sucht in der Umgebung eines Feldes nach dem Roboter mit der hoechsten
	 * Prioritaet aus dem gegnerischen Team.
	 * @param pos die Position des Feldes
	 * @return die Richtung des Roboters in Bezug auf das Feld
	 *         <nobr>(<code>x</code>, <code>y</code>)</nobr>, oder
	 *         <code>NOTHING</code>, falls es keine benachbarten gegnerischen
	 *         Roboter gibt.
	 * @see #nextOtherRobotAround(int, int)
	 * @see #nextOtherRobotAround(int)
	 * @see #getPriority
	 * @see #nextRobotAround
	 * @see #nextOtherRobotAround
	 */
	protected final int nextOtherRobotAround(Point pos)
	{
		return nextOtherRobotAround(pos.x, pos.y);
	}


	/**
	 * Sucht in der Umgebung eines Feldes nach dem Roboter mit der hoechsten
	 * Prioritaet aus dem gegnerischen Team.
	 * @param x die X-Koordinate des Feldes
	 * @param y die Y-Koordinate des Feldes
	 * @return die Richtung des Roboters in Bezug auf das Feld
	 *         <nobr>(<code>x</code>, <code>y</code>)</nobr>, oder
	 *         <code>NOTHING</code>, falls es keine benachbarten gegnerischen
	 *         Roboter gibt.
	 * @see #nextOtherRobotAround(Point)
	 * @see #nextOtherRobotAround(int)
	 * @see #getPriority
	 * @see #nextRobotAround
	 * @see #nextOtherRobotAround
	 */
	protected final int nextOtherRobotAround(int x, int y)
	{
		return nextTeamRobotAround(x, y, OTHER_TEAM);
	}


	/**
	 * Sucht in der Umgebung eines Feldes, das neben diesem Roboter liegt, nach dem Roboter
	 * mit der hoechsten Prioritaet aus dem gegnerischen Team.
	 * @param direction die Richtung, in der das Feld liegt
	 * @return die Richtung des Roboters in Bezug auf das Feld, oder
	 *         <code>NOTHING</code>, falls es keine benachbarten gegnerischen
	 *         Roboter gibt.
	 * @see #nextOtherRobotAround(Point)
	 * @see #nextOtherRobotAround(int, int)
	 * @see #getPriority
	 * @see #nextRobotAround
	 * @see #nextOwnRobotAround
	 */
	protected final int nextOtherRobotAround(int direction)
	{
		return nextOtherRobotAround(offsetPoint(getMyPos(), direction));
	}



	/**
	 * Prueft, ob und mit welcher Aktion dieser Roboter ein Tor machen koennte.
	 * @return Eine <code>SoccerAction</code>, die den Zug beschreibt, mit dem
	 *         ein Tor erzielt werden kann, oder <code>null</code>, falls dies
	 *         nicht moeglich ist.
	 * @see #canScoreGoal(Point)
	 * @see #canScoreGoal(int, int)
	 * @see SoccerAction
	 */
	protected final SoccerAction canScoreGoal()
	{
		return canScoreGoal(getMyPos());
	}


	/**
	 * Prueft, ob und mit welcher Aktion ein Roboter auf einem bestimmten Feld
	 * ein Tor machen koennte.
	 * @param pos die Position des Roboters
	 * @return Eine <code>SoccerAction</code>, die den Zug beschreibt, mit dem
	 *         ein Tor erzielt werden kann, oder <code>null</code>, falls dies
	 *         nicht moeglich ist.
	 * @see #canScoreGoal()
	 * @see #canScoreGoal(int, int)
	 * @see SoccerAction
	 */
	protected final SoccerAction canScoreGoal(Point pos)
	{
		int team = lookAt(pos);
		int ballAround = selfBallAround();

		if (team == MY_TEAM)
		{
			if (pos.y == 2 && ballAround == UP && lookToRel(pos, UP, 2) == FREE )
			{
				return SoccerAction.move(UP);
			}
			else if (pos.y == 1 && ballAround != NOTHING && lookToRel(pos, UP) == FREE)
			{
				return SoccerAction.dribble(UP);
			}
			else if (pos.y == 0 && ballAround == DOWN)
			{
				if (lookToRel(pos, LEFT) == FREE)
					return SoccerAction.dribble(LEFT);
				else if (lookToRel(pos, RIGHT) == FREE)
					return SoccerAction.dribble(RIGHT);
				else if (lookToRel(pos, DOWN, 2) != FREE)
					return SoccerAction.move(DOWN);
			}
		}
		else if (team == OTHER_TEAM)
		{
			if (pos.y == getFieldHeight() - 3 && ballAround == DOWN &&
				lookToRel(pos, DOWN, 2) == FREE )
			{
				return SoccerAction.move(DOWN);
			}
			else if (pos.y == getFieldHeight() - 2 && ballAround != NOTHING &&
				lookToRel(pos, DOWN) == FREE)
			{
				return SoccerAction.dribble(DOWN);
			}
			else if (pos.y == getFieldHeight() - 1 && ballAround == UP)
			{
				if (lookToRel(pos, LEFT) == FREE)
					return SoccerAction.dribble(LEFT);
				else if (lookToRel(pos, RIGHT) == FREE)
					return SoccerAction.dribble(RIGHT);
				else if (lookToRel(pos, UP, 2) != FREE)
					return SoccerAction.move(UP);
			}
		}
		return null;
	}


	/**
	 * Prueft, ob und mit welcher Aktion ein Roboter auf einem bestimmten Feld
	 * ein Tor machen koennte.
	 * @param x die X-Koordinate des Roboters
	 * @param y die Y-Koordinate des Roboters
	 * @return Eine <code>SoccerAction</code>, die den Zug beschreibt, mit dem
	 *         ein Tor erzielt werden kann, oder <code>null</code>, falls dies
	 *         nicht moeglich ist.
	 * @see #canScoreGoal()
	 * @see #canScoreGoal(Point)
	 * @see SoccerAction
	 */
	protected final SoccerAction canScoreGoal(int x, int y)
	{
		return canScoreGoal(new Point(x, y));
	}


	/**
	 * Errechnet einen Punkt, der, ausgehend von einem anderen Punkt, in eine
	 * bestimmte Richtung verschoben wurde.<br/>
	 * Es wird keinerlei ueberpruefung durchgefuehrt, ob der zurueckgegebene Punkt
	 * noch innerhalb des Spielfelds liegt.
	 * @param p der Ausgangspunkt
	 * @param direction Die Richtung. Dies kann <code>UP</code>, <code>DOWN</code>,
	 * <code>LEFT</code>, <code>RIGHT</code> oder <code>NOTHING</code> sein.
	 * @param dist die Anzahl der Felder, in die der Punkt verschoben werden soll
	 *             (kann auch negativ sein)
	 * @return Der neue Punkt. Dieser ist in der angegebenen Richtung von
	 *         <code>p</code> und <code>dist</code> Felder entfernt.
	 */
	protected static Point offsetPoint(Point p, int direction, int dist)
	{
		switch (direction)
		{
			case LEFT:
				return new Point(p.x - dist, p.y);
			case RIGHT:
				return new Point(p.x + dist, p.y);
			case UP:
				return new Point(p.x, p.y - dist);
			case DOWN:
				return new Point(p.x, p.y + dist);
			case NOTHING:
				return new Point(p);
			default:
				throw new IllegalArgumentException("Ungueltige Richtung");
		}
	}


	/**
	 * Errechnet einen Punkt, der, ausgehend von einem anderen Punkt, um ein Feld
	 * in eine bestimmte Richtung verschoben wurde.<br/>
	 * Es wird keinerlei ueberpruefung durchgefuehrt, ob der zurueckgegebene Punkt
	 * noch innerhalb des Spielfelds liegt.
	 * @param p der Ausgangspunkt
	 * @param direction Die Richtung. Dies kann <code>UP</code>, <code>DOWN</code>,
	 * <code>LEFT</code>, <code>RIGHT</code> oder <code>NOTHING</code> sein.
	 * @return Der neue Punkt. Dieser liegt in der angegebenen Richtung direkt
	 *         neben <code>p</code>.
	 */
	protected static Point offsetPoint(Point p, int direction)
	{
		return offsetPoint(p, direction, 1);
	}
	
	
	/**
	 * Errechnet einen Punkt, der, von diesem Roboter, in einer bestimmten Richtung und
	 * einer bestimmten Entfernung liegt.
	 * @param direction Die Richtung. Dies kann <code>UP</code>, <code>DOWN</code>,
	 *                  <code>LEFT</code>, <code>RIGHT</code> oder <code>NOTHING</code>
	 *                  sein.
	 * @param dist die Entfernung (kann auch negativ sein)
	 * @return Der neue Punkt. Dieser liegt in der angegebenen Richtung und der angegebenen
	 *         Entfernung von diesem Roboter. 
	 */
	protected Point offsetPoint(int direction, int dist)
	{
		return offsetPoint(getMyPos(), direction, dist);
	}
	
	
	/**
	 * Errechnet einen Punkt, in einer bestimmten Richtung direkt neben diesem
	 * Roboter liegt.
	 * @param direction Die Richtung. Dies kann <code>UP</code>, <code>DOWN</code>,
	 *                  <code>LEFT</code>, <code>RIGHT</code> oder <code>NOTHING</code>
	 *                  sein.
	 * @return Der neue Punkt. Dieser liegt in der angegebenen Richtung direkt neben
	 *         diesem Roboter.
	 */
	protected Point offsetPoint(int direction)
	{
		return offsetPoint(getMyPos(), direction);
	}


	/**
	 * Bestimmt die Richtung, in der ein Punkt aus Sicht eines anderen Punkts
	 * liegt.
	 * @param from der Ausgangspunkt
	 * @param to der Punkt, dessen relative Richtung ermittelt werden soll.
	 * @return Eine bitweise OR-Kombination (Operator <code>|</code>) der Werte
	 * <code>UP</code>, <code>DOWN</code>, <code>LEFT</code>,
	 * <code>RIGHT</code>. Um die Richtungsanteile zu ermitteln, koennen die
	 * Konstanten <code>HORIZONTAL</code> und <code>VERTICAL</code> als Masken
	 * benutzt werden.<br/>
	 * Liegt z.B. <code>to</code> von <code>from</code> aus links oben, so wird
	 * <code>LEFT | UP</code> zur�ckgegeben.<br/>
	 * Stimmen <code>from</code> und <code>to</code> ueberein, ist der Rueckgabewert
	 * <code>NOTHING</code>.
	 */
	protected final int getRelativeDirection(Point from, Point to)
	{
		int direction = NOTHING;
		if (to.x < from.x)
			direction |= LEFT;
		else if (to.x > from.x)
			direction |= RIGHT;
		if (to.y < from.y)
			direction |= UP;
		else if (to.y > from.y)
			direction |= DOWN;
		return direction;
	}
	
	
	/** Sendet eine Nachricht an einen befreundeten Roboter. Die Nachricht ist fuer
	 * den Mitspieler nur einmal verfuegbar, und zwar wenn er das naechste Mal am Zug ist;
	 * danach wird sie geloescht.<br/>
	 * Das Format der Nachricht (z.B. ein String) kann dabei selbst definiert werden
	 * und sollte vom anderen Roboter entsprechend interpretiert werden koennen.<br/>
	 * Der andere Roboter erfuehrt dabei <em>nicht</em>, von welchem Absender die
	 * Nachricht stammt. Ist diese Information wichtig, sollte sie in der Nachricht
	 * vorkommen.<br/>
	 * Es kann an jeden Roboter pro Runde nur eine Nachricht gesendet werden. Wird
	 * <code>sendMessageTo</code> dann erneut fuer den selben Empfaenger aufgerufen,
	 * wird die alte Nachricht ueberschrieben.
	 * @param pos die Position des befreundeten Roboters
	 * @param message die Nachricht (kann auch <code>null</code> sein)
	 * @return <code>true</code>, falls die Nachricht gesendet wurde; <code>false</code>,
	 * falls an der Stelle <code>pos</code> kein eigener Roboter steht.
	 * @see #sendMessageToAll
	 * @see #receiveMessages
	 */
	protected final boolean sendMessageTo(Point pos, Object message)
	{
		Team myTeam = gameInfo.getPlayerAt(getMyPos(), this).getTeam();
		RuntimePlayer otherPlayer = gameInfo.getPlayerAt(pos, this);
		if ( otherPlayer != null &&
			otherPlayer.getPlayer() != this &&
			otherPlayer.getTeam().equals(myTeam) )
		{
			otherPlayer.addMessage(this, message);
			return true;
		}
		else
			return false;		
	}
	
	
	/** Sendet eine Nachricht an alle befreundeten Roboter. Die Nachricht ist fuer die
	 * Mitspieler nur einmal verfuegbar, und zwar jeweils bei ihrem naechsten Spielzug;
	 * danach wird sie geloescht.
	 * Das Format der Nachricht (z.B. ein String) kann dabei selbst definiert werden
	 * und sollte von den anderen Robotern entsprechend interpretiert werden koennen.<br/>
	 * Die anderen Roboter erfahren dabei <em>nicht</em>, von welchem Absender die
	 * Nachricht stammt. Ist diese Information wichtig, sollte sie in der Nachricht
	 * vorkommen.<br/>
	 * Roboter, die gerade wegen einer roten Karte aussetzen muessen, erhalten die
	 * Nachricht <em>nicht</em>.
	 * @param message die Nachricht (kann auch <code>null</code> sein)
	 * @see #sendMessageTo
	 * @see #receiveMessages
	 */
	protected final void sendMessageToAll(Object message)
	{
		Collection<RuntimePlayer> teamRobots = gameInfo.getFieldRobots(MY_TEAM, this);
		for (Iterator<RuntimePlayer> i = teamRobots.iterator(); i.hasNext(); )
		{
			RuntimePlayer otherPlayer = (RuntimePlayer) i.next();
			if (otherPlayer.getPlayer() != this)
				otherPlayer.addMessage(this, message);
		}
	}
	
	
	/** Holt alle Nachrichten ab, die seit dem letzten Spielzug dieses Roboters an ihn
	 * gesendet wurden.
	 * Die Nachrichten sind nur in dieser Runde verfuegbar. Es bleibt dem Roboter selbst
	 * ueberlassen, ob er etwas mit diesen Informationen anfaengt.
	 * @return Ein Array, das alle Nachrichten an diesen Roboter enthaelt. Wurden keine
	 *         Nachrichten gesendet, wird ein Array der Laenge 0 zurueckgegeben.
	 * @see #sendMessageTo
	 * @see #sendMessageToAll
	 */
	protected final Object[] receiveMessages()
	{
		RuntimePlayer myRobot = gameInfo.getPlayerAt(getMyPos(), this);
		return myRobot.getMessages();
	}

}
